﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace QLearningGame
{
    class HelicopterEnvironment : Reinforcement.CDEnvironment
   {
        #region Fields

        // Const values
        const double acceleration = 0.2;
        const double engineAcceleration = 0.4;
        const float numStepsToObstacle = 60f;
        Point viewportSize;

        // Current state info

        /// <summary>
        /// Horizontal speed
        /// </summary>
        double HelicopterSpeed = 0;

        /// <summary>
        /// Speed by which obstacle approaches
        /// </summary>
        float ObstacleSpeed;


        /// <summary>
        /// Is there collision with obstacle / roof / ceiling
        /// </summary>
        bool IsCollision
        {
            get
            {
                double thickTop1 = Thickness(true, TopShiftNormalized + (Helicopter.X) / (double)viewportSize.X) * viewportSize.Y / 2.0f;
                double thickTop2 = Thickness(true, TopShiftNormalized + (Helicopter.X + Helicopter.Width) / (double)viewportSize.X) * viewportSize.Y / 2.0f;
                double thickBottom = Thickness(false, BottomShiftNormalized + Helicopter.X / (double)viewportSize.X) * viewportSize.Y / 2.0f;

                if (Helicopter.Intersects(Obstacle) ||
                    Helicopter.Y < 0 ||
                    Helicopter.Y > viewportSize.Y - Helicopter.Height ||
                    Helicopter.Y < thickTop1 ||
                    Helicopter.Y < thickTop2 ||
                    Helicopter.Y + Helicopter.Height > viewportSize.Y - thickBottom)
                    return true;
                return false;
            }
        }

        double TopShift;
        double TopShiftNormalized { get {return TopShift / viewportSize.X; } }
        double BottomShift;
        double BottomShiftNormalized { get {return TopShift / viewportSize.X; } }

        /// <summary>
        /// Describes helicopter's position and size
        /// </summary>
        Rectangle Helicopter = new Rectangle(50, 0, 0, 0);

        /// <summary>
        /// Describes the nearest obstacle's position size
        /// </summary>
        Rectangle Obstacle = new Rectangle();

        /// <summary>
        /// Distance to obstacle. Also might be zero or negative.
        /// </summary>
        double ObstacleDistance
        {
            get { return Obstacle.Left - (Helicopter.Left + Helicopter.Width); }
        }

        int seedTop = 123;
        int seedBottom = 456;

        /// <summary>
        /// Decides whether state extension values will be calculated.
        /// </summary>
        public bool CalculateForVisuals = false;

        #endregion

        #region Reinforcement.CDEnvironment interface implementation

        public void CDStartInRandomState()
        {
            // Floor/ceiling thickness function - randomize
            Random r = new Random();
            seedTop = r.Next(1000);
            seedBottom = r.Next(500);

            // Helicopter in safe position
            this.Helicopter.Y = viewportSize.Y / 2;

            // Reset speed
            HelicopterSpeed = 0;

            // Reset obstacle
            ResetObstacle(true);

            // Random top / bottom shift
            this.TopShift = viewportSize.X / 2; // viewportSize.Value.X * r.NextDouble()/2.0; //
            //this.BottomShift = viewportSize.Value.X; //;
        }

        /// <summary>
        /// Returns the numbers describing the current state parameters. State extensions returns values in
        /// a given viewport's coordinate system.
        /// </summary>
        /// <param name="state">Values in this array are assigned as descibed below:
        /// state[0] = speed (vertical axis)
        /// state[1] = distance to the "ceiling" (measured in front of helicopter)
        /// state[2] = distance to the "floor" (measured in front of helicopter)
        /// state[3] = distance to the nearest obstacle (horizontal axis)
        /// state[4] = space between the nearest obstacle and "ceiling"
        /// state[5] = space between the nearest obstacle and "floor"
        /// state[6] = space between obstacle's center and ceiling (measured in front of obstacle)
        /// state[7] = space between obstacle center and floor (measured in front of obstacle)
        /// </param>
        /// <param name="state_extension">
        /// Additional data to allow showing process as helicopter flight animation
        /// state_extension[0] = ceiling thickness at the viewport's beginning
        /// state_extension[1] = ceiling thickness at the viewport's middle
        /// state_extension[2] = ceiling thickness at the viewport's end
        /// state_extension[3] = floor thickness at the viewport's beginning
        /// state_extension[4] = floor thickness at the viewport's middle
        /// state_extension[5] = floor thickness at the viewport's end
        /// state_extension[6] = helicopter's distance from top of the viewport
        /// state_extension[7] = obstacle: x
        /// state_extension[8] = obstacle: y
        /// </param>
        public void CDGetCurrentState(ref double[] state, ref double[] state_extension)
        {
            int ahead = 30; // in pixels (arbitrary);

            // Prędkość helikoptera
            state[0] = HelicopterSpeed;
            // Odległość od sufitu z przodu
            state[1] = Helicopter.Top - NormalizeY(Thickness(true, NormalizeX(TopShift + Helicopter.X + ahead))); 
            // Odległość od podłogi z przodu
            state[2] = ( viewportSize.Y - NormalizeY(Thickness(false, NormalizeX(BottomShift + Helicopter.X + ahead))))
                - (Helicopter.Top + Helicopter.Height);
            // Odległość od przeszkody
            state[3] = ObstacleDistance;
            // Odległość przeszkody od sufitu
            state[4] = Obstacle.Top - NormalizeY(Thickness(true, NormalizeX(TopShift + Obstacle.X)));
            // Odległość przeszkody od podłogi
            state[5] = (viewportSize.Y - NormalizeY(Thickness(false, NormalizeX(BottomShift + Obstacle.X))))
                - (Obstacle.Top + Obstacle.Height);
            // Odległość pomiędzy środkiem przeszkody a sufitem
            state[6] = Obstacle.Y + Obstacle.Height / 2;
                //- NormalizeY(Thickness(true, NormalizeX(TopShift + Obstacle.X)));
            // Odległość pomiędzy środkiem przeszkody a podłogą
            //state[7] = (viewportSize.Y - NormalizeY(Thickness(false, NormalizeX(BottomShift + Obstacle.X))))
            //   - (Obstacle.Y + Obstacle.Height / 2);
            state[7] = viewportSize.Y- (Obstacle.Y + Obstacle.Height / 2);
            // od środka przezskody do wys. helikoptera
            

            // Include data for state visualization. Skipped during the learning process for performance.
            if (this.CalculateForVisuals)
            {
                state_extension[0] = Thickness(true, TopShiftNormalized);
                state_extension[1] = Thickness(true, TopShiftNormalized + 0.5);
                state_extension[2] = Thickness(true, TopShiftNormalized + 1);

                state_extension[3] = Thickness(false, BottomShiftNormalized);
                state_extension[4] = Thickness(false, BottomShiftNormalized + 0.5);
                state_extension[5] = Thickness(false, BottomShiftNormalized + 1);

                state_extension[6] = this.Helicopter.Y;
                state_extension[7] = this.Obstacle.X;
                state_extension[8] = this.Obstacle.Y;

                state_extension[9] = (Math.Ceiling(TopShiftNormalized) - TopShiftNormalized)*2 - 1;
                state_extension[10] = (BottomShiftNormalized - Math.Floor(BottomShiftNormalized)) * 2 - 1;
            }


        }

        private double NormalizeY(double p)
        {
            return p * viewportSize.Y / 2.0f;
        }

        private double NormalizeX(double p)
        {
            return (p / (double) viewportSize.X);
        }

        /// <summary>
        /// Whenever called, returns state description in a string form (easy to read for human, eg. in debugger)
        /// </summary>
        public override string ToString()
        {
            double[] state = new double[8];
            double[] stateExt = new double[11];
            
            // Get state
            CDGetCurrentState(ref state, ref stateExt);

            // Format string
            string s = String.Format("Prędkość: {0}, odl. suf: {1}, odl. podł.: {2},\n"+
                "Odl. przeszk: {3}, odl przeszk. suf.: {4}, odl przeszk. podł.: {5}\n"+
                "Odl. śr prz. suf: {6}, Odl. śr prz podł: {7}",
                state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]);
            
            return s;
        }


        // Define other methods and classes here
        public double Thickness(bool top, double position)
        {
            int randSeed = top ? seedTop : seedBottom;

            int floor = (int)Math.Floor(position);
            int ceil = (int)Math.Ceiling(position);
            if (ceil == floor) ceil++; // graniczny

            Random rFloor = new Random(randSeed + floor);
            Random rCeil = new Random(randSeed + ceil);

            double f = rFloor.NextDouble() / 2.5;
            double c = rCeil.NextDouble() / 2.5;

            double ret = c * (position - floor) + f * (ceil - position);
            //Console.WriteLine("* f: " + f + ", c:  " + c + ", out = " + ret + " tmp " + 1);
            return ret;
        }

        /// <summary>
        /// Executes the decision and returns the information whether crash occured
        /// </summary>
        /// <param name="decision">1 for "go up", 0 for "do nothing"</param>
        /// <param name="reward">0 if no crash occured, 1 if the helicopter crashed</param>
        public void CDMove(int decision, ref double reward)
        {
            // Change helicopter position
            double moveAcceleration = acceleration - engineAcceleration * decision;
            HelicopterSpeed = HelicopterSpeed + moveAcceleration;
            Helicopter.Y += (int) HelicopterSpeed;

            // Change obstacle position
            // Obstacle disappeared on the left of the screen => put new one on the right
            if (Obstacle.X + Obstacle.Width < 0)
                ResetObstacle(false);
            // Move obstacle closer (~it's speed)
            Obstacle.X = (int) (Obstacle.X - ObstacleSpeed);

            // Change roof positions
            TopShift += ObstacleSpeed;
            BottomShift += ObstacleSpeed;
            
            // Set reward
            reward = 0;

            if (IsCollision)
            {
                reward = -100;
                CDStartInRandomState();
            }
        }

        private void ResetObstacle(bool randomState)
        {
            Random r = new Random();
            Obstacle.X = randomState ? (int)(((double)(viewportSize.X - Obstacle.Width)) * r.NextDouble()) : viewportSize.X - Obstacle.Width;

            int yPosition = r.Next(0, Convert.ToInt32(viewportSize.Y - Obstacle.Height));
            Obstacle.Y = yPosition;
        }

        #endregion


        #region Visualization support

        public void setViewportSize(Point size)
        {
            this.viewportSize = size;
            this.ObstacleSpeed = (viewportSize.X / numStepsToObstacle);
        }

        public void SetHelicopterSize(Vector2 size)
        {
            this.Helicopter.Width = (int)size.X;
            this.Helicopter.Height = (int)size.Y;
        }

        public void SetObstacleSize(Vector2 size)
        {
            Obstacle.Width = Convert.ToInt32(size.X);
            Obstacle.Height = Convert.ToInt32(size.Y);
        }

        public float GetHelicopterLeft()
        {
            return Helicopter.X;
        }
        
        #endregion
    }
}
